package net.itxxw.auth.service;

import com.alibaba.fastjson.JSON;
import net.itxxw.framework.client.itxxwServiceList;
import net.itxxw.framework.domain.ucenter.ext.AuthToken;
import net.itxxw.framework.domain.ucenter.response.AuthCode;
import net.itxxw.framework.exception.ExceptionCast;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author：ycpan
 * @date：Created in 2021/5/25 18:48
 * @description：
 * @modified By：
 * @version: $
 */
@Service
public class AuthService {
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthService.class);

    @Value("${auth.tokenValiditySeconds}")
    int tokenValiditySeconds;
    //Eureka负载均衡客户端
    @Autowired
    LoadBalancerClient loadBalancerClient;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;


    /**
     * 用户登陆认证实现
     * @param username 用户名
     * @param password 密码
     * @param clientId 客户端id
     * @param clientSecret 客户端凭证
     * @return AuthToken
     */
    public AuthToken login(String username, String password, String clientId, String clientSecret) {
        //1.申请令牌
        AuthToken authToken = this.appleToken(username, password, clientId, clientSecret);
        if(authToken == null){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        //2.保存令牌到redis
        String access_token=authToken.getAccess_token();
        String content =JSON.toJSONString(authToken);
        boolean saveTokenResult = this.saveToken(access_token,content,tokenValiditySeconds);
        if(!saveTokenResult){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        return authToken;
    }


    //第一个方法：向Oauth2服务申请令牌
    private AuthToken appleToken(String username, String password, String clientId, String clientSecret) {
        //采用客户端负载均衡的方式从eureka获取认证服务的ip和端口
        ServiceInstance serviceInstance = loadBalancerClient.choose(itxxwServiceList.ITXXW_SERVICE_UCENTER_AUTH);
        if (serviceInstance == null) {
            LOGGER.error("choose an auth instance fail");
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_AUTHSERVER_NOTFOUND);
        }
        //获取令牌的url
        String path = serviceInstance.getUri().toString()+"/auth/oauth/token";

        //定义body
       MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        //授权方式
        formData.add("grant_type","password");
        //账号
        formData.add("username",username);
        //密码
        formData.add("password",password);
        //定义头
        MultiValueMap<String,String> header=new LinkedMultiValueMap<>();
        header.add("Authorization",httpbasic(clientId,clientSecret));
        //指定 restTemplate当遇到400或401响应时候也不要抛出异常，也要正常返回值
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override
            public void handleError(ClientHttpResponse response) throws IOException{
                //当响应值为400或401时候也要正常响应，不要抛出错误
                if(response.getRawStatusCode()!=400&&response.getRawStatusCode()!=401){}
                 super.handleError(response);
            }
        });
        Map map=null;
        try {
            //http请求 远程调用令牌 spring security的申请令牌接口参数说明：
         /*   url：请求路径
            method：请求的方法（GET、POST、PUT等）
            requestEntity：HttpEntity对象，封装了请求头和请求体
            responseType：返回数据类型
            uriVariables：支持PathVariable类型的数据。*/
            ResponseEntity<Map> mapResponseEntity=restTemplate.exchange(path,HttpMethod.POST,
                    new HttpEntity<MultiValueMap<String,String>>(formData,header),Map.class);
            map=mapResponseEntity.getBody();
        }catch (RestClientException e){
            e.printStackTrace();
            LOGGER.error("request oauth_token_password error:{}",e.getMessage());
            e.printStackTrace();
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }

        if(map == null || map.get("access_token") == null || map.get("refresh_token") == null || map.get("jti") == null)
        {//jti是jwt令牌的唯一标识作为用户身份令牌
            //获取spring security返回的错误信息
            String error_description = (String) map.get("error_description");
            if(StringUtils.isNotEmpty(error_description)){
                if(error_description.equals("坏的凭证")){
                    ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
                }else if(error_description.indexOf("UserDetailsService returned null")>=0){
                    ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
                }
            }
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }

        AuthToken authToken=new AuthToken();
        //访问令牌
       String jwt_token = (String) map.get("access_token");
        //刷新令牌(jwt)
        String refresh_token = (String) map.get("refresh_token");
        //jti，作为用户的身份标识
        String access_token = (String) map.get("jti");
        authToken.setJwt_token(jwt_token);
        authToken.setAccess_token(access_token);
        authToken.setRefresh_token(refresh_token);
        return authToken;
    }


    //第二个方法：储存令牌到redis
    private boolean saveToken(String access_token,String content,long ttl){
        //储存到redis的key
        //令牌名称
        String name= "user_token:" + access_token;
        //保存到令牌到redis
        stringRedisTemplate.boundValueOps(name).set(content,ttl, TimeUnit.SECONDS);  //调试到这里出错

        Map<String,String> valueMap = new HashMap<>();
        //获取过期时间
        Long expire = stringRedisTemplate.getExpire(name);
        return expire>0;
    }


    //获取httpbasic认证串
    private String httpbasic(String clientId, String clientSecret){
        //将客户端id和客户端密码拼接，按“客户端id:客户端密码”
        String string = clientId+":"+clientSecret;
        //进行base64编码
        byte[] encode = Base64.encode(string.getBytes());
        return "Basic "+new String(encode);
    }

    //从redis查询令牌
    public AuthToken getUserToken(String token){
        String userToken = "user_token:"+token;
        String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
        if(userToken!=null){
            AuthToken authToken = null;
            try {
                authToken = JSON.parseObject(userTokenString, AuthToken.class);
            } catch (Exception e) {
                LOGGER.error("getUserToken from redis and execute JSON.parseObject error{}",e.getMessage());
                e.printStackTrace();
            }
            return authToken;
        }
        return null;
    }

    /**
     * 删除指定usertoken在redis中的jwt信息
     * @param uid usertoken
     * @return
     */
    public Boolean delToken(String uid) {
        String key = "user_token:" + uid;
        Boolean delete = stringRedisTemplate.delete(key);
        return delete;
    }


}
